/*
 * DAQClient.cpp
 *
 *  Created on: May 10, 2012
 *      Author: dblyth
 *
 *  Description:	This file is a script for connecting to the server.
 */

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

#include <TApplication.h>
#include <TCanvas.h>
#include <TColor.h>
#include <TCondition.h>
#include <TDirectory.h>
#include <TGButton.h>
#include <TGFrame.h>
#include <TGLabel.h>
#include <TGLayout.h>
#include <TGListBox.h>
#include <TGProgressBar.h>
#include <TGStatusBar.h>
#include <TGTab.h>
#include <TGTextEntry.h>
#include <TH1.h>
#include <TMessage.h>
#include <TMutex.h>
#include <TRootEmbeddedCanvas.h>
#include <TSocket.h>
#include <TSystem.h>
#include <TTimer.h>

struct configRegister {
	Int_t id;
	string name;
	string address;
	string value;
	string dataWidth;
};

class ClientApp {
public:
	ClientApp(const char* host, int port) {
		mainFrame = 0;
		timer = 0;
		regListSize = 0;
		regList = new configRegister*[regListSize];

		cout << "Connecting..." << endl;
		sock = new TSocket(host, port);
		if (sock->GetErrorCode() < 0) {
			delete sock;
			sock = 0;
			cerr << "Couldn't connect, exiting" << endl;
			this->exitClient();
			return;
		}

		mainFrame = new TGMainFrame(gClient->GetRoot(), 800, 600);
		mainFrame->SetCleanup(kDeepCleanup);
		mainFrame->Connect("CloseWindow()", "ClientApp", this, "exitClient()");

		statusBar = new TGStatusBar(mainFrame);
		mainFrame->AddFrame(statusBar, new TGLayoutHints(kLHintsBottom | kLHintsExpandX));
		statusBar->SetText("Ready");

		TGHorizontalFrame* mainHF = new TGHorizontalFrame(mainFrame);
		mainFrame->AddFrame(mainHF);

		TGVerticalFrame* verticalBF = new TGVerticalFrame(mainHF, 100);
		mainHF->AddFrame(verticalBF, new TGLayoutHints(kLHintsExpandY));

		addButton(verticalBF, "Start run", "ClientApp", this, "startRun()");
		addButton(verticalBF, "Stop run", "ClientApp", this, "stopRun()");
		addButton(verticalBF, "Update current histo", "ClientApp", this, "updateHistogram()");
		addButton(verticalBF, "Update all histos", "ClientApp", this, "updateAllHisto()");
		addButton(verticalBF, "Reset histograms", "ClientApp", this, "resetHisto()");
		addButton(verticalBF, "Reset registers", "ClientApp", this, "resetRegisters()");
		addButton(verticalBF, "Terminate server", "ClientApp", this, "termServer()");

		tabFrame = new TGTab(mainHF);
		mainHF->AddFrame(tabFrame);

		TGCompositeFrame* runFrame = tabFrame->AddTab("Run");
		runFrame->SetLayoutManager(new TGHorizontalLayout(runFrame));
		TGVerticalFrame* runVFLeft = new TGVerticalFrame(runFrame);
		runFrame->AddFrame(runVFLeft, new TGLayoutHints(0, 10, 10, 10, 10));

		duration = createBasicEntry(runVFLeft, "Run duration (s):", "-1");
		numPedSamples = createBasicEntry(runVFLeft, "Number of leading pedestal samples:", "20");

		TGHorizontalFrame* ADCBaseAddrHF = new TGHorizontalFrame(runVFLeft);
		runVFLeft->AddFrame(ADCBaseAddrHF, new TGLayoutHints(0, 10, 10, 5, 5));
		TGLabel* ADCBaseAddrLabel = new TGLabel(ADCBaseAddrHF, "ADC base address:");
		ADCBaseAddrHF->AddFrame(ADCBaseAddrLabel, new TGLayoutHints(0, 3, 3, 0, 0));
		ADCBaseAddr = new TGTextEntry(ADCBaseAddrHF);
		ADCBaseAddrHF->AddFrame(ADCBaseAddr, new TGLayoutHints(0, 3, 3, 0, 0));

		TGHorizontalFrame* addRegisterHF = new TGHorizontalFrame(runVFLeft);
		runVFLeft->AddFrame(addRegisterHF, new TGLayoutHints(0, 10, 10, 5, 5));
		addRegisterName = new TGTextEntry(addRegisterHF);
		addRegisterHF->AddFrame(addRegisterName, new TGLayoutHints(0, 3, 3, 0, 0));
		addRegisterName->SetTitle("Name");
		addRegisterAddress = new TGTextEntry(addRegisterHF);
		addRegisterHF->AddFrame(addRegisterAddress, new TGLayoutHints(0, 3, 3, 0, 0));
		addRegisterAddress->SetTitle("Address");
		addRegisterValue = new TGTextEntry(addRegisterHF);
		addRegisterHF->AddFrame(addRegisterValue, new TGLayoutHints(0, 3, 3, 0, 0));
		addRegisterValue->SetTitle("Value");
		addRegisterDataWidth = new TGTextEntry(addRegisterHF);
		addRegisterHF->AddFrame(addRegisterDataWidth, new TGLayoutHints(0, 3, 3, 0, 0));
		addRegisterDataWidth->SetTitle("Data width");
		TGTextButton* button = addButton(addRegisterHF, "Add new register", "ClientApp", this,
				"addRegisterFromFields()");

		addRegisterName->Connect("TabPressed()", "TGTextEntry", addRegisterAddress, "SelectAll()");
		addRegisterName->Connect("TabPressed()", "TGTextEntry", addRegisterAddress, "SetFocus()");
		addRegisterAddress->Connect("TabPressed()", "TGTextEntry", addRegisterValue, "SelectAll()");
		addRegisterAddress->Connect("TabPressed()", "TGTextEntry", addRegisterValue, "SetFocus()");
		addRegisterName->Connect("ReturnPressed()", "TGTextButton", button, "Clicked()");
		addRegisterAddress->Connect("ReturnPressed()", "TGTextButton", button, "Clicked()");
		addRegisterValue->Connect("ReturnPressed()", "TGTextButton", button, "Clicked()");

		TGHorizontalFrame* registerHF = new TGHorizontalFrame(runVFLeft);
		runVFLeft->AddFrame(registerHF, new TGLayoutHints(0, 10, 10, 5, 5));
		registerLB = new TGListBox(registerHF);
		registerLB->Resize(250, 440);
		registerLB->Connect("Selected(Int_t)", "ClientApp", this, "selectRegister(Int_t)");
		registerHF->AddFrame(registerLB, new TGLayoutHints(0, 3, 3, 0, 0));
		registerValue = new TGTextEntry(registerHF);
		registerHF->AddFrame(registerValue, new TGLayoutHints(0, 3, 3, 0, 0));
		registerValue->Connect("TextChanged(char*)", "ClientApp", this, "changeRegisterValue(char*)");
		registerDataWidth = new TGTextEntry(registerHF);
		registerHF->AddFrame(registerDataWidth, new TGLayoutHints(0, 3, 3, 0, 0));
		registerDataWidth->Connect("TextChanged(char*)", "ClientApp", this, "changeRegisterDataWidth(char*)");
		addButton(registerHF, "Delete register", "ClientApp", this, "deleteRegister()");

		send("getADCBaseAddr");
		char msg[32];
		recv(msg, 32);
		if (string(msg).compare("error") != 0)
			ADCBaseAddr->SetText(msg);

		resetRegisters();

		TGVerticalFrame* runVFRight = new TGVerticalFrame(runFrame);
		runFrame->AddFrame(runVFRight, new TGLayoutHints(0, 10, 10, 10, 10));

		isRunning = addStatus(runVFRight, "Is running:", "");
		eventCount = addStatus(runVFRight, "Event count:", "");
		runName = addStatus(runVFRight, "RunName:", "");

		TGLabel* ADCBuffBarLabel = new TGLabel(runVFRight, "ADC buffer");
		runVFRight->AddFrame(ADCBuffBarLabel, new TGLayoutHints(kLHintsLeft, 0, 0, 10, 0));
		ADCBuffBar = new TGHProgressBar(runVFRight);
		ADCBuffBar->SetPosition(50);
		runVFRight->AddFrame(ADCBuffBar, new TGLayoutHints(kLHintsExpandX));

		TGLabel* memBuffBarLabel = new TGLabel(runVFRight, "Memory buffer");
		runVFRight->AddFrame(memBuffBarLabel, new TGLayoutHints(kLHintsLeft));
		memBuffBar = new TGHProgressBar(runVFRight);
		memBuffBar->SetPosition(50);
		runVFRight->AddFrame(memBuffBar, new TGLayoutHints(kLHintsExpandX));

		resetHisto(false);

		updateAllStatus();

		mainFrame->MapSubwindows();
		mainFrame->Resize(mainFrame->GetDefaultSize());
		mainFrame->MapWindow();

		timer = new TTimer();
		timer->Connect("Timeout()", "ClientApp", this, "updateAllStatus()");
		timer->Start(1000, 0);
	}

	virtual ~ClientApp() {
		if (mainFrame) {
			mainFrame->Cleanup();
			delete mainFrame;
		}
		if (timer) {
			timer->Stop();
			delete timer;
		}
		if (regList) {
			for (int i = 0; i < regListSize; i++)
				delete regList[i];
			delete[] regList;
		}

		if (sock) {
			send("exit");
			char mess[32];
			recv(mess, 32);
			sock->Close();
			delete sock;
		}
	}

	void exitClient() {
		delete this;
	}

	void startRun() {
		send("startRun");

		char msg[32];
		recv(msg, 32);
		string flag = msg;
		if (flag.compare("OK") != 0) {
			statusBar->SetText((string("Start run: ") + flag).c_str());
			sock->Clear();
			return;
		}

		send(numPedSamples->GetText());

		send(duration->GetText());

		send(ADCBaseAddr->GetText());

		for (int i = 0; i < regListSize; i++)
			send(
					(regList[i]->name + "\t" + regList[i]->address + "\t" + regList[i]->value + "\t"
							+ regList[i]->dataWidth).c_str());

		send("done");

		recv(msg, 32);
		statusBar->SetText((string("Start run: ") + msg).c_str());

		sock->Clear();
	}

	void stopRun() {
		simpleCommand("stopRun", "Stop run");
	}

	void updateAllStatus() {
		send("getIsRunning");
		char msg[32];
		recv(msg, 32);
		stringstream ss;
		ss.width(12);
		ss << msg;
		isRunning->SetText(ss.str().c_str());

		send("getEventCount");
		recv(msg, 32);
		ss.str("");
		ss.width(11);
		ss << msg;
		eventCount->SetText(ss.str().c_str());

		send("getRunName");
		recv(msg, 32);
		ss.str("");
		ss.width(15);
		ss << msg;
		runName->SetText(ss.str().c_str());

		send("getADCBufferLevel");
		recv(msg, 32);
		ADCBuffBar->SetPosition(atof(msg));
		ADCBuffBar->ShowPos(true);

		send("getMemBufferLevel");
		recv(msg, 32);
		memBuffBar->SetPosition(atof(msg));
		memBuffBar->ShowPos(true);
	}

	void updateHistogram(const char* charName = "", bool update = true) {
		string name;
		if (strlen(charName) == 0)
			name = tabFrame->GetCurrentTab()->GetString();
		else
			name = charName;

		string status = "Update ";
		bool ok = false;

		if (name.compare("Run") != 0) {
			string command = "update," + name;
			send(command.c_str());

			TCanvas* canvas =
					((TRootEmbeddedCanvas*) tabFrame->GetTabContainer(name.c_str())->GetFrameFromPoint(0, 0))->GetCanvas();

			TMessage* mess;
			sock->Recv(mess);
			if (mess) {
				if (mess->What() == kMESS_OBJECT) {
					TH1* histo = (TH1*) mess->ReadObject(mess->GetClass());
					if (histo) {
						canvas->cd();
						histo->DrawCopy();
						if (update) {
							canvas->Modified();
							canvas->Update();
						}
						delete histo;
						ok = true;
					}
				}
				delete mess;
			}

			status += name;
		} else
			status += "histo";

		if (ok)
			status += ": OK";
		else
			status += ": FAIL";

		statusBar->SetText(status.c_str());
	}

	void updateAllHisto() {
		int numTabs = tabFrame->GetNumberOfTabs();
		for (int i = 1; i < numTabs; i++)
			updateHistogram(tabFrame->GetTabTab(i)->GetString());
	}

	void resetHisto(bool resize = true) {
		while (tabFrame->GetNumberOfTabs() > 1)
			tabFrame->RemoveTab(1, false);

		send("getHistos");
		char histoNameChar[32];
		recv(histoNameChar, 32);
		string histoName = histoNameChar;
		while (histoName.compare("done") != 0 && histoName.compare("error") != 0) {
			addHistogramTab(histoName.c_str());
			recv(histoNameChar, 32);
			histoName = histoNameChar;
		}

		tabFrame->MapSubwindows();
		tabFrame->Layout();
		if (resize)
			mainFrame->Resize(mainFrame->GetDefaultSize());

		updateAllHisto();
	}

	void selectRegister(Int_t id) {
		string value;
		string dataWidth;
		for (int i = 0; i < regListSize; i++)
			if (regList[i]->id == id) {
				value = regList[i]->value;
				dataWidth = regList[i]->dataWidth;
				break;
			}

		registerValue->SetText(value.c_str());
		registerDataWidth->SetText(dataWidth.c_str());
	}

	void resetRegisters() {
		registerLB->RemoveAll();

		for (int i = 0; i < regListSize; i++)
			delete regList[i];
		delete[] regList;

		regListSize = 0;
		regList = new configRegister*[regListSize];

		send("getRegisters");
		char buffer[256];
		recv(buffer, 256);
		string msg = buffer;
		while (msg.compare("done") != 0 && msg.compare("error") != 0) {
			size_t pos = msg.find('\t');
			string name = msg.substr(0, pos);
			msg = msg.substr(pos + 1);
			pos = msg.find('\t');
			string address = msg.substr(0, pos);
			msg = msg.substr(pos + 1);
			pos = msg.find('\t');
			string value = msg.substr(0, pos);
			msg = msg.substr(pos + 1);

			addRegister(name.c_str(), address.c_str(), value.c_str(), msg.c_str());

			recv(buffer, 256);
			msg = buffer;
		}

		statusBar->SetText(("Reset registers: " + msg).c_str());
	}

	void addRegisterFromFields() {
		addRegister(addRegisterName->GetText(), addRegisterAddress->GetText(), addRegisterValue->GetText(),
				addRegisterDataWidth->GetText());
	}

	void deleteRegister() {
		Int_t id = registerLB->GetSelected();
		if (id == -1)
			return;
		registerLB->RemoveEntry(id);
		registerLB->Layout();
		registerValue->SetText("");

		configRegister** tempRegList = new configRegister*[regListSize - 1];

		int i;
		for (i = 0; i < regListSize - 1; i++)
			if (regList[i]->id != id)
				tempRegList[i] = regList[i];
			else
				break;

		delete regList[i];

		for (i++; i < regListSize; i++)
			tempRegList[i - 1] = regList[i];

		delete[] regList;
		regList = tempRegList;
		regListSize--;
	}

	void changeRegisterValue(const char* newValue) {
		Int_t id = registerLB->GetSelected();
		if (id == -1)
			return;

		for (int i = 0; i < regListSize; i++)
			if (regList[i]->id == id) {
				regList[i]->value = newValue;
				break;
			}
	}

	void changeRegisterDataWidth(const char* newValue) {
		Int_t id = registerLB->GetSelected();
		if (id == -1)
			return;

		for (int i = 0; i < regListSize; i++)
			if (regList[i]->id == id) {
				regList[i]->dataWidth = newValue;
				break;
			}
	}

	void termServer() {
		simpleCommand("termServer", "Terminate server");
	}

private:
	TGLabel* addStatus(TGCompositeFrame* parent, const char* name, const char* value) {
		TGHorizontalFrame* HF = new TGHorizontalFrame(parent);
		parent->AddFrame(HF, new TGLayoutHints(0, 10, 10, 5, 5));
		TGLabel* nameLabel = new TGLabel(HF, name);
		HF->AddFrame(nameLabel, new TGLayoutHints(0, 3, 3, 0, 0));
		TGLabel* valueLabel = new TGLabel(HF, value);
		HF->AddFrame(valueLabel, new TGLayoutHints(kLHintsExpandX, 3, 3, 0, 0));
		valueLabel->SetTextColor(kBlue);
		return valueLabel;
	}

	TGTextButton* addButton(TGCompositeFrame* parent, const char* text, const char* className, void* object,
			const char* slot, ULong_t hints = kLHintsExpandX) {
		TGTextButton* button = new TGTextButton(parent, text);
		button->Connect("Clicked()", className, object, slot);
		parent->AddFrame(button, new TGLayoutHints(hints, 3, 3, 0, 0));
		return button;
	}

	void addHistogramTab(const char* name) {
		TGCompositeFrame* frame = tabFrame->AddTab(name);
		TRootEmbeddedCanvas* ECanvas = new TRootEmbeddedCanvas((string(name) + " Canvas").c_str(), frame, 800, 600);
		frame->AddFrame(ECanvas);
	}

	void addRegister(const char* name, const char* address, const char* value, const char* dataWidth) {
		configRegister* reg = new configRegister;
		reg->id = regListSize - 1;
		reg->name = name;
		reg->address = address;
		reg->value = value;
		reg->dataWidth = dataWidth;

		bool uniqueID = false;
		while (!uniqueID) {
			reg->id++;
			uniqueID = true;
			for (int i = 0; i < regListSize; i++)
				if (reg->id == regList[i]->id) {
					uniqueID = false;
					break;
				}
		}

		registerLB->AddEntry((string(name) + " (" + address + ")").c_str(), reg->id);
		registerLB->MapSubwindows();
		registerLB->Layout();

		configRegister** tempRegList = new configRegister*[regListSize + 1];
		for (int i = 0; i < regListSize; i++)
			tempRegList[i] = regList[i];
		delete[] regList;
		regList = tempRegList;
		regList[regListSize] = reg;
		regListSize++;
	}

	TGTextEntry* createBasicEntry(TGCompositeFrame* parent, const char* label, const char* initial) {
		TGHorizontalFrame* HF = new TGHorizontalFrame(parent);
		parent->AddFrame(HF, new TGLayoutHints(0, 10, 10, 5, 5));
		TGLabel* labelFrame = new TGLabel(HF, label);
		HF->AddFrame(labelFrame, new TGLayoutHints(0, 3, 3, 0, 0));
		TGTextEntry* entry = new TGTextEntry(HF);
		HF->AddFrame(entry, new TGLayoutHints(0, 3, 3, 0, 0));
		entry->SetText(initial);
		return entry;
	}

	void simpleCommand(const char* command, const char* name) {
		send(command);

		char mess[32];
		recv(mess, 32);

		statusBar->SetText((string(name) + ": " + mess).c_str());
	}

	void send(const char* msg) {
		if (sock) {
			sock->Send(msg);
			if (sock->GetErrorCode() != 0) {
				sock->Close();
				delete sock;
				sock = NULL;
			}
		}
	}

	void recv(char* msg, int msgLength) {
		bool fail = true;

		if (sock) {
			sock->Recv(msg, msgLength);
			if (sock->GetErrorCode() == 0)
				fail = false;
			else {
				sock->Close();
				delete sock;
				sock = NULL;
			}
		}

		if (fail)
			if (msgLength > 5)
				strcpy(msg, "error");
	}

	configRegister** regList;
	int regListSize;

	TGHProgressBar* ADCBuffBar;
	TGHProgressBar* memBuffBar;
	TGLabel* isRunning;
	TGLabel* eventCount;
	TGLabel* runName;
	TGListBox* registerLB;
	TGMainFrame* mainFrame;
	TGStatusBar* statusBar;
	TGTab* tabFrame;
	TGTextEntry* registerValue;
	TGTextEntry* registerDataWidth;
	TGTextEntry* addRegisterName;
	TGTextEntry* addRegisterAddress;
	TGTextEntry* addRegisterValue;
	TGTextEntry* addRegisterDataWidth;
	TGTextEntry* ADCBaseAddr;
	TGTextEntry* duration;
	TGTextEntry* numPedSamples;

	TSocket* sock;
	TTimer* timer;
}
;

void DAQClient(const char* host = "localhost", int port = 1024) {
	ClientApp* app = new ClientApp(host, port);
	(void) app;
}
